#!/bin/sh
#
# Check what's installed on a PCP/PCPQA VM looking for missing apps
# and packages
#

# same function is in allow-pmlc-access ... need to track changes
#
_getnetworkaddr()
{
    __verbose=false
    $very_verbose && __verbose=true
    if `which hostname >/dev/null 2>&1`
    then
	host=`hostname`
	if `which host >/dev/null 2>&1`
	then
	    host_out=`host $host`
	    if echo "$host_out" | grep ' has address ' >/dev/null
	    then
		addr=`echo "$host_out" | sed -e 's/.*address //'`
		$__verbose && echo "_getnetworkaddr: host -> addr=$addr" >&2
		if `which ifconfig >/dev/null 2>&1`
		then
		    # ifconfig line of interest looks like:
		    # inet 192.168.1.100  netmask 255.255.255.0  ...
		    # or
		    # inet addr:192.168.1.224  Bcast:192.168.1.255  Mask:255.255.255.0
		    # or
		    # inet 192.168.1.238/24 broadcast 192.168.1.255 flags 0x0
		    #
		    __config=`ifconfig | grep "[ :]$addr[ /]"`
		    if echo "$__config" | grep '[Mm]ask' >/dev/null
		    then
			mask=`echo "$__config" | sed -e 's/.*ask[ :]\([^ ][^ ]*\).*/\1/'`
		    elif echo "$__config" | grep " $addr/" >/dev/null
		    then
			mask=`echo "$__config" | sed -e '/\/24/s/.*/255.255.255.0/'`
		    else
			echo "_getnetworkaddr: botched config line: $__config" >&2
			mask=''
		    fi
		    $__verbose && echo "_getnetworkaddr: ifconfig -> mask=$mask" >&2
		    case "$mask"
		    in
			255.255.255.0|0xffffff00|ffffff00)	# /24 network
			    echo "$addr" | sed -e 's/\.[0-9]*$/.*/'
			    ;;
			# pmcd's [access] is not smart enough to handle other
			# than /24 networks, so map the other likely options
			# to the broader /24 network
			#
			255.255.255.128|255.255.255.192|255.255.255.224|255.255.255.240|255.255.255.248|255.255.255.252|255.255.255.254)
			    echo "$addr" | sed -e 's/\.[0-9]*$/.*/'
			    ;;
			*)
			    echo >&2 "_getnetworkaddr: Warning: cannot handle network mask: $mask"
			    ;;
		    esac
		elif `which ip >/dev/null 2>&1`
		then
		    # ip line of interest looks like:
		    # 4: br0    inet 192.168.1.100/24 ...
		    #
		    mask=`ip -f inet -o address | grep " $addr/" | sed -e 's/.*inet //' -e 's/[0-9.][0-9.]*\/\([^ ][^ ]*\) .*/\1/'`
		    $__verbose && echo "_getnetworkaddr: ip -> mask=$mask" >&2
		    if [ "$mask" != 24 ]
		    then
			# pmcd's [access] is not smart enough to handle other
			# than /24 networks, so map the other likely options
			echo >&2 "_getnetworkaddr: Warning: cannot handle network mask: $mask"
		    fi
		    # /24 netmask
		    mask=255.255.255.0
		    echo "$addr" | sed -e 's/\.[0-9]*$/.*/'
		else
		    echo >&2 "Neither ifconfig(1) nor ip(1)? Not sure how to get primary ip addr and netmask"
		fi
	    else
		echo >&2 "Unexpected host(1) output: $host_out ... cannot get ip addr and netmask"
	    fi
	else
	    echo >&2 "No host(1)? Not sure how to get primary ip addr and netmask"
	fi
    else
	echo >&2 "No hostname(1)? Not sure how to get primary ip addr and netmask"
    fi
}

_usage()
{
    echo "Usage: $0 [options]"
    echo "  -a           (all) ignore packages already installed"
    echo "  -b           base package set, skip the N/A and optional ones"
    echo "  -f           force, don't try to guess the version of Python, Qt, ..."
    echo "  -M           do not list the meta-package alternatives (cpan, perl, pip3)"
    echo "  -m manifest  use alternative manifest file"
    echo "  -o otherdir  use alternative other-packages directory"
    echo "  -p           generate list of likely packages that should be"
    echo "               installed with dnf or apt-get or yum or ..."
    echo "  -P           same as -p, but emit shell commands for installer"
    echo "  -r require   use alternative require file"
    echo "  -s skip      use alternative skip file"
    echo "  -S skipbase  use alternative basename for skipbase.<hostname> files"
    echo "  -u unavail   use alternative unavailable file"
    echo "  -v           verbose (debugging)"
    exit 1
}

# version1 is on line 1
# version2 is on line 2
# relop is "<", "<=", "=", ">=" or ">"
# return value is 0 (true) if version1 relop version2 is true
# else return value is 1
#
_compare()
{
    relop="$1"
    awk -F. >$tmp.compare '
NR == 1	{ for (i = 1; i <= NF; i++)
	    v[i] = $i
	  nv = NF
	  next
	}
NR == 2	{ nf = NF
	  if (nv > nf) nf = nv
	  for (i = 1; i <= nf; i++) {
	    if (v[i]+0 == $i+0) continue
	    if (v[i]+0 < $i+0) {
		print "<"
		exit
	    }
	    if (v[i]+0 > $i+0) {
		print ">"
		exit
	    }
	  }
	  print "="
	}'
    ret=1
    case `cat $tmp.compare`
    in
	"<")
	    [ "$relop" = "<" -o "$relop" = "<=" ] && ret=0
	    ;;
	"=")
	    [ "$relop" = "=" -o "$relop" = "<=" -o "$relop" = ">=" ] && ret=0
	    ;;
	">")
	    [ "$relop" = ">" -o "$relop" = ">=" ] && ret=0
	    ;;
	*)
	    echo "Arrgh ... installed version $version, want $relop $specversion,"
	    echo "but failed to extract relop (`cat $tmp.compare`)"
	    ;;
    esac
    return $ret
}

# create $tmp.allpkgs if we know how to do that ...
#
_build_allpkgs()
{
    case "$distro"
    in
    Ubuntu|Debian|LinuxMint)
	apt-cache dumpavail | sed -n -e '/^Package: /s///p' >$tmp.allpkgs
	;;

    RHEL|Fedora|CentOS|openSUSE|SUSE\ SLES)
	if which zypper >/dev/null 2>&1
	then
	    : TODO
	elif which dnf >/dev/null 2>&1
	then
	    ( dnf list available; dnf list installed ) 2>&1 \
	    | sed >$tmp.allpkgs \
		-e '1,/^[^ ]* Packages/d' \
		-e 's/ .*//' \
		-e 's/\.[^.]*$//' \
	    # end
	elif which yum >/dev/null 2>&1
	then
	    ( yum list available; yum list installed ) 2>&1 \
	    | sed >$tmp.allpkgs \
		-e '1,/^[^ ]* Packages/d' \
		-e 's/ .*//' \
		-e 's/\.[^.]*$//' \
	    # end
	fi
	;;

    NetBSD)
	# TODO
	;;

    FreeBSD)
	# TODO
	;;

    OpenBSD)
	# expect lines like this ...
	# <tr>...<a href="libsysstat-0.4.1p1.tgz">libsysstat-0.4.1p1.tgz</a>...
	#
	url="`cat /etc/installurl`/`uname -r`/packages/`uname -m`/"
	$verbose && echo >&2 "Fetching list of available packages from $url ..."
	curl -s $url \
	| sed -n >$tmp.allpkgs \
	    -e '/a href=".*tgz">/{
s/\.tgz<\/a>.*/.tgz/
s/.*>//
s/-[0-9].*//
p
}'
	;;

    Gentoo)
	# TODO
	;;

    Darwin)
	# TODO
	;;

    OpenIndiana)
	# TODO
	;;

    Slackware)
	# lines after awk look like ...
	# ./aspell-word-lists/aspell-cs-20040614_1-x86_64-5.txz
	# ./n/openldap-client-2.4.42-x86_64-1.txz
	#
	for f in /var/lib/slackpkg/slackware64-filelist.gz \
		 /var/lib/slackpkg/extra-filelist.gz
	do
	    [ -f "$f" ] || continue
	    zcat "$f" \
	    | $PCP_AWK_PROG '{ print $1 }' \
	    | sed \
		-e 's/\-[0-9].*//' \
		-e 's;.*/;;' \
	    # end
	done >$tmp.allpkgs
	;;

    ArchLinux)
	pacman -Ss . \
	| sed -n >$tmp.allpkgs -e '/^[^ ]/s/ .*//p'
	;;

    esac
}

# need to be careful ... $1 uses an extended regexp notation where
# things we want to match as regexps are enclosed in {...} but regexp
# characters like ., [ and * elsewhere needed to be treated as literals
# and then we have to do word-bounded matching so something like
# foo.{.*} matches foo.bar only from the line
# ... [ ... foo.bar ... foolbar ... blah.something ... ]
#
_safe_pattern()
{
    echo "$1" \
    | $PCP_AWK_PROG '
	{ len = length($0)
	  out = ""
	  inregexp = 0
	  for (i = 1; i <= len; i++) {
	    c = substr($0, i, 1)
	    if (inregexp == 0 && c == "{") {
		inregexp = 1
		continue
	    }
	    if (inregexp == 1 && c == "}") {
		inregexp = 0
		continue
	    }
	    if (inregexp == 0) {
		# not in { ... } so need to escape grep/sed regexp specials
		if (c == "[" || c == "*" || c == ".") {
		    if (out == "")
			out = "\\"
		    else
			out = out "\\"
		}
	    }
	    if (inregexp == 1) {
		if (c == ".") {
		    # . here needs to match a non-space character to avoid
		    # matching more than one word and not consume the ]
		    # at the end the list of packages from the the manifest
		    # line
		    if (out == "")
			out = "[^] ]"
		    else
			out = out "[^] ]"
		    continue
		}
	    }
	    if (out == "")
		out = c
	    else
		out = out c
	  }
	  print out
	}'
}

# for some virgin installs, cmp(1) may not be there (yet), so this is
# an attempt to resolve that issue ... we are only interested in a binary
# outcome, namely if the two files ($1 and $2) are the same, return 0
# else return 1
#
# try in order: cmp, diff, sum, stat (only size can be compared)
#
_cmp()
{
    sts=1
    if which cmp >/dev/null 2>&1
    then
	cmp "$1" "$2" >/dev/null 2>&1 && sts=0
    elif which diff >/dev/null 2>&1
    then
	diff "$1" "$2" >/dev/null 2>&1 && sts=0
    elif which sum >/dev/null 2>&1
    then
	[ "`sum <"$1"`" = "`sum <"$2"`" ] && sts=0
    elif which stat >/dev/null 2>&1
    then
	s1=`stat "$1" | sed -n -e '/^ *Size:/{
s/^ *Size: *//
s/ .*//p
}'`
	s2=`stat "$2" | sed -n -e '/^ *Size:/{
s/^ *Size: *//
s/ .*//p
}'`
	[ "$s1" = "$s2" ] && sts=0
    fi
    return $sts
}

# Usage: _cull_from_manifest pkg msg
# if [...] is empty afterwards, replace [] by [msg]
# reads $tmp.manifest, writes $tmp.culled
#
_cull_from_manifest()
{
    _pkg="$1"
    _msg="`echo $2 | sed -e 's;/;\\\\/;g'`"
    _pkg_pat=`_safe_pattern "$_pkg"`
    sed <$tmp.manifest >$tmp.culled -e "/[[ ]$_pkg_pat[] ]/"'{
s/\([[ ]\)'"$_pkg_pat"'\([] (]\)/\1\2/g
s/\[  */[/
s/\[or  */[/
s/  *]/]/
s/  *or *]/]/
s/  *or *(/ (/
s/  *or  *or  */ or /
s/\[ *or *]/[]/
s/\[]/['"$_msg"']/
s/\[([^)]*)]/['"$_msg"']/
}'
    if $very_verbose
    then
	echo "Culling $_pkg ($_pkg_pat)"
	if _cmp $tmp.manifest $tmp.culled >/dev/null
	then
	    : no diffs
	else
	    diff $tmp.manifest $tmp.culled
	fi
    fi
}

# Networking goo
#
_check_host()
{
    ipaddr=`sed -n </etc/hosts -e '/^#/d' -e '/::/d' -e 's/$/ /' -e "/[ 	]$1[ 	]/"'{
s/[ 	].*//
p
}'`
    if [ -z "$ipaddr" ]
    then
	echo "No /etc/hosts entry for $1"
	return
    fi

    if [ `echo "$ipaddr" | wc -l | sed -e 's/  *//g'` -gt 1 ]
    then
	echo "Multiple /etc/hosts entries for $1"
	return
    fi

    rm -f $tmp.tmp
    if `which ifconfig >/dev/null 2>&1`
    then
	# ifconfig lines of interest look like
	# br0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        #	inet 192.168.1.100  netmask 255.255.255.0  broadcast 192.168.1.255
        # ...
	# lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
	#	inet 127.0.0.1  netmask 255.0.0.0
	#
	ifconfig >$tmp.tmp
    elif `which ip >/dev/null 2>&1`
    then
	ip -f inet address >$tmp.tmp
	# ip lines of interest look like
	# 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc ...
	#     inet 127.0.0.1/8 scope host lo
	#     ...
	# 4: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc ...
	#     inet 192.168.1.100/24 brd 192.168.1.255 scope global br0
	#     ...
    else
	echo >&2 "Neither ifconfig(1) nor ip(1)? Not sure how to get primary ip addr"
	return
    fi
    sed <$tmp.tmp \
	-e 's/^[0-9][0-9]*: //' \
	-e 's/: / /g' \
	-e '/ inet /s/\/.*/ /' \
    | awk >&2 '
/^[^ 	]/	{ iface = $1; next }
/inet addr:'$ipaddr' / || /inet '$ipaddr'[ \/]/ {
			  if (iface == "lo")
			    print "Warning: '$1' associated with loopback network interface"
			  found = 1
			  exit
			}
END		{ if (found != 1)
		    print "Warning: '$1' ('$ipaddr') not associated with a network interface"
		}'
}

#
# --- MAINLINE ---
#

export LC_COLLATE=POSIX

if [ -f /etc/pcp.conf ]
then
    . /etc/pcp.conf
else
    # punt
    #
    case `uname`
    in
	Darwin|*BSD)
	    PCP_ECHO_PROG=echo
	    PCP_ECHO_N=
	    PCP_ECHO_C='\c'
	    ;;
	*)
	    PCP_ECHO_PROG=echo
	    PCP_ECHO_N=-n
	    PCP_ECHO_C=
	    ;;
    esac
fi

# Need directory where this script is located so we can find the other
# scripts and control files
#
home=`echo $0 | sed -e 's/\/*check-vm$//'`
if [ ! -f $home/whatami ]
then
    echo >&2 "Botch: \$0=$0 -> bad \$home=$home ?"
    exit 1
fi

if [ ! -f $home/packages.rc ]
then
    echo >&2 "Botch: cannot find $home/packages.rc"
    exit
fi

. $home/packages.rc

_setversions
# $distro, $version and $arch now set

all=false
basic=false
force=false
pkglist=false
pkgcmd=false
verbose=false
very_verbose=false
check_manifest=false
check_manifest_args=''
manifest="$home"/other-packages/manifest		# default, see -m
otherdir=other-packages					# default, see -o
require="$home"/other-packages/require			# default, see -r
skip="$home"/other-packages/skip			# default, see -s
skipbase="$home"/other-packages/skip			# default, see -S
unavailable="$home"/other-packages/unavailable		# default, see -u
include_meta=true
while getopts 'abcfm:Mo:pPr:s:S:u:v?' p
do
    case "$p"
    in
	a)
		all=true
		;;

	b)
		basic=true
		;;

	c)	# exec check-manifest when manifest has been build
		# ... expert only, so not even mentioned in Usage
		#
		check_manifest=true
		;;

	f)
		force=true
		;;

	M)	include_meta=false
		;;

	m)	# alternate manifest
		manifest="$OPTARG"
		;;

	o)	# alternate other-packages directory
		otherdir="$OPTARG"
		;;

	p)	# just list most likely package for yum, dnf, apt-get, ...
		pkglist=true
		;;

	P)	# same as -P but emit shell commands for installer
		pkgcmd=true
		pkglist=true
		;;

	r)	# alternate require
		require="$OPTARG"
		;;

	s)	# alternate skip
		skip="$OPTARG"
		;;

	S)	# alternate skip basename
		skipbase="$OPTARG"
		;;

	u)	# alternate unavailable
		unavailable="$OPTARG"
		;;

	v)	if $verbose
		then
		    very_verbose=true
		else
		    verbose=true
		fi
		check_manifest_args="$check_manifest_args -v"
		;;

	?)	_usage
		# NOTREACHED
    esac
done
shift `expr $OPTIND - 1`
[ $# -eq 0 ] || _usage

if $very_verbose
then
    tmp=tmp
else
    tmp=/var/tmp/$$
    trap "rm -f $tmp.*; exit 0" 0 1 2 3 15
fi
rm -f $tmp.*

if [ -f /etc/pcp.conf ]
then
    . /etc/pcp.conf
else
    # punt!
    #
    if which gawk >/dev/null 2>&1
    then
	PCP_AWK_PROG=gawk
    else
	PCP_AWK_PROG=awk
    fi
fi

# add additional and optional directories
for dir in /sbin /usr/sbin
do
    if [ -d "$dir" ]
    then
	if echo ":$PATH:" | grep -q ":$dir:"
	then
	    :
	else
	    export PATH="$PATH:$dir"
	    #debug# echo add $dir to \$PATH
	fi
    fi
done

if [ ! -f "$manifest" ]
then
    echo "Botch: cannot find manifest: $manifest"
    exit 1
fi
if [ ! -f "$require" ]
then
    echo "Botch: cannot find require: $require"
    exit 1
fi
if [ ! -f "$unavailable" ]
then
    echo "Botch: cannot find unavailable: $unavailable"
    exit 1
fi
if [ ! -f "$skip" ]
then
    echo "Botch: cannot find skip: $skip"
    exit 1
fi

# Packaging types we know about.
#	dpkg - Debian-based
#	rpm - RPM-based, e.g. RHEL, CentOS, SuSE, Fedora
#	emerge - Gentoo
#	pkgin - NetBSD
#	pkg_add - OpenBSD
#	F_pkg - pkg(1) on FreeBSD
#	S_pkg - pkg(1) on OpenIndiana
#	slackpkg - Slackware
#	pacman - Arch Linux
#	brew - Mac OS X
#	urpmi - OpenMandriva (also RPM based)
#
# later on we will select EXACTLY one of the $pkgtypes to be $mytype
#
pkgtypes="dpkg rpm urpmi emerge pkgin pkg_add F_pkg S_pkg slackpkg pacman brew"

# Base on platform info, select the packaging type that applies and generate the
# list of currently installed packages in $tmp.installed
#
mytype=''
installcmd=''
rm -f $tmp.installed
case "$distro"
in
    Ubuntu|Debian|LinuxMint)
	mytype=dpkg
	installcmd="apt install"
	# just ones like
	# xterm install ok installed
	# and skip ones like
	# zfsutils-linux deinstall ok config-files
	#
	dpkg-query -W -f '${package} ${status}\n' \
	| sed -n -e '/ installed$/s/ .*//p' >$tmp.installed
	;;

    RHEL|Fedora|CentOS|openSUSE|SUSE\ SLES)
	mytype=rpm
	if which zypper >/dev/null 2>&1
	then
	    installcmd="zypper install"
	elif which dnf >/dev/null 2>&1
	then
	    installcmd="dnf install"
	elif which yum >/dev/null 2>&1
	then
	    installcmd="yum install"
	fi
	rpm -qa --qf '%{NAME}\n' >$tmp.installed
	;;

    Gentoo)
	mytype=emerge
	installcmd="emerge --ask"
	equery list '*' | sed -e 's/.*].*] //' -e 's/-[0-9].*//' >$tmp.installed
	;;

    NetBSD)
	mytype=pkgin
	installcmd="pkgin -y install"
	pkgin list | sed -e 's/-[0-9].*//' >$tmp.installed
	;;

    OpenBSD)
	mytype=pkg_add
	installcmd="pkg_add"
	pkg_info -a | sed -e 's/-[0-9].*//' >$tmp.installed
	;;

    FreeBSD)
	mytype=F_pkg
	installcmd="pkg install"
	pkg info -a | sed -e 's/-[0-9].*//' >$tmp.installed
	;;

    OpenIndiana)
	mytype=S_pkg
	installcmd="pkg install"
	pkg list -H | sed -e 's/ .*//' >$tmp.installed
	;;

    Slackware)
	mytype=slackpkg
	installcmd="slackpkg install"
	ls /var/log/packages | sed -e 's/-[0-9].*//' >$tmp.installed
	;;

    ArchLinux)
	mytype=pacman
	installcmd="pacman -S"
	pacman -Q -i | sed -n -e '/^Name /s/.* : //p' >$tmp.installed
	;;

    Darwin)
	mytype=brew
	installcmd="brew install"
	echo >&2 "TODO: no list all packages method for brew"
	;;

    OpenMandriva)
	mytype=urpmi
	installcmd="urpmi install"
	urpmi -qa --qf '%{NAME}\n' >$tmp.installed
	;;
esac
if [ -z "$mytype" ]
then
    echo "$0: Botch: don't recognize mytype for: $distro"
    exit
fi
if [ -z "$installcmd" ]
then
    echo "$0: Botch: don't recognize installcmd for: $distro"
    exit
fi
if [ ! -f $tmp.installed ]
then
    echo "$0: Botch: can't generate tmp.installed for: $distro"
    exit
fi

# Distros we know about (or more specifically ones with the same 
# packaging tools but different package names).
#
#	debian		Debian, Ubuntu, LinuxMint, ...
#	redhat		RedHat or Fedora
#	centos		CentOS
#	suse		SLES or OpenSuSE
#	arch		Arch Linux
#	mandriva	OpenMandriva
#	freebsd		FreeBSD
#	netbsd		NetBSD
#	openbsd		OpenBSD
#
tags="debian redhat centos suse arch mandriva freebsd netbsd openbsd"

# Append original line numbers for non-blank lines in the manifest.
# Also rewrite [? ...] (package unknown) to [?:<original line number> ...]
# for easier mapping back to the manifest file.
#
$PCP_AWK_PROG 'NF > 0 { gsub("\\[\\?", "[?:" NR); print $0 " :" NR }' <$manifest >$tmp.manifest

if which $PCP_PYTHON_PROG >/dev/null 2>&1
then
    # For python-ctypes, check for python before 2.5 ... expect something like
    # Python 2.7.3
    eval `$PCP_PYTHON_PROG -V 2>&1 | sed -e 's/Python //' -e 's/^/maj=/' -e 's/\./ min=/' -e 's/\..*//'`
    if [ -n "$maj" -a -n "$min" ]
    then
	rm -f $tmp.need
	if [ "$maj" -lt 2 ]
	then
	    touch $tmp.need
	elif [ "$maj" -eq 2 -a "$min" -lt 5 ]
	then
	    touch $tmp.need
	fi
	[ -f $tmp.need ] && \
	    echo "rpm?	/usr/share/doc/python-ctypes*	[python-ctypes]" >>$tmp.manifest
    fi
fi

# Strip the packaging-dependent lines that do not apply ...
# this is an optimization remove checks from the main loop below
#
rm -f $tmp.ok
for pkgtype in $pkgtypes
do
    want=false
    [ "$pkgtype" = "$mytype" ] && want=true
    if $want
    then
	$very_verbose && echo >&2 "include ${pkgtype}? lines"
	sed -e "s/^${pkgtype}?[ 	]//" <$tmp.manifest >$tmp.tmp
	mv $tmp.tmp $tmp.manifest
	touch $tmp.ok
    else
	#debug# $very_verbose && echo >&2 "exclude ${pkgtype}? lines"
	grep -v "^${pkgtype}?[ 	]" <$tmp.manifest >$tmp.tmp
	mv $tmp.tmp $tmp.manifest
    fi
done
if [ ! -f $tmp.ok ]
then
    echo >&2 "Warning: don't understand what packing is being used here ..."
    echo >&2 "         it is none of: $pkgtypes"
fi

# Strip the distro-dependent lines that do not apply ...
# this is an optimization remove checks from the main loop below
#
# tests here are copied from whatami
#
iam=''
if [ -f /etc/SuSE-release ]
then
    iam=suse
elif [ -f /etc/centos-release ]
then
    iam=centos
elif [ -f /etc/redhat-release ]
then
    iam=redhat
elif [ -f /etc/debian_version ]
then
    iam=debian
elif [ -f /etc/mandriva-release ]
then
    iam=mandriva
elif [ -f /etc/gentoo-release ]
then
    iam=gentoo
elif [ -f /etc/fedora-release ]
then
    iam=fedora
elif [ -f /etc/os-release ]
then
    iam=opensuse
elif [ -f /etc/release ]
then
    iam=openindiana
elif [ -f /etc/slackware-version ]
then
    iam=slackware
elif [ -f /etc/arch-release ]
then
    iam=arch
elif [ -f /etc/lsb-release ]
then
    iam=`sed </etc/lsb-release -n -e '/^DISTRIB_ID *= */s///p' | tr '[A-Z]' '[a-z]'`
fi
#debug# $very_verbose && echo >&2 "distro iam=$iam"

for tag in $tags
do
    pick=false
    case "$iam"
    in
	redhat|fedora)
	    [ "$tag" = redhat ] && pick=true
	    ;;
	*)
	    [ "$tag" = "$iam" ] && pick=true
	    ;;
    esac
    if $pick
    then
	$very_verbose && echo >&2 "include ${tag}? lines"
	sed -e "s/^${tag}?[ 	]//" <$tmp.manifest >$tmp.tmp
	mv $tmp.tmp $tmp.manifest
    else
	#debug# $very_verbose && echo >&2 "exclude ${tag}? lines"
	grep -v "^${tag}?[ 	]" <$tmp.manifest >$tmp.tmp
	mv $tmp.tmp $tmp.manifest
    fi
done

# Strip comments ...
#
sed -e '/^#/d' <$tmp.manifest >$tmp.tmp
mv $tmp.tmp $tmp.manifest

# the "unavailable" file records packages that are not available for a
# particular distribution and version and architecture ... 
#
_parse_file $unavailable >$tmp.pkgs
if [ -s $tmp.pkgs ]
then
    unavail_pkg="`cat $tmp.pkgs`"
fi
$very_verbose && echo >&2 "unavailable: $unavail_pkg"

# the "require" file records packages that are must be installed for a
# particular distribution and version and archtecture ... 
#
require_pkg=''
_parse_file $require >$tmp.pkgs
if [ -s $tmp.pkgs ]
then
    $very_verbose && echo >&2 "require: `cat $tmp.pkgs`"
    for _need in `cat $tmp.pkgs`
    do
	if grep "^$_need\$" $tmp.installed >/dev/null
	then
	    :
	else
	    if [ -z "$require_pkg" ]
	    then
		require_pkg="$_need"
	    else
		require_pkg="$require_pkg $_need"
	    fi
	fi
    done
    if $very_verbose
    then
	if [ -n "$require_pkg" ]
	then
	    echo >&2 "require and not installed: $require_pkg"
	else
	    echo >&2 "require and not installed: <none>"
	fi
    fi
fi

if $pkglist
then
    # for -p, don't mark manifest lines with N/A ... this is handled
    # later for each package in [ ... ]
    #
    :
else
    if [ -n "$unavail_pkg" ]
    then
	for _pkg in $unavail_pkg
	do
	    if [ -z "$version" ]
	    then
		_cull_from_manifest "$_pkg" "N/A on $distro"
	    else
		_cull_from_manifest "$_pkg" "N/A on $distro $version"
	    fi
	    mv $tmp.culled $tmp.manifest
	done
    fi
fi

# the optional "skip.<hostname>" file records packages that may be available
# but for various reasons should be be installed on a particular host ...
# examples are docker.io that does not get along with qemu or unbound that
# does not co-exist with bind9 or libfooqt.so that is really for the wrong
# version of Qt
#
skip_pkg=''
_hostname=`hostname -s`
if [ -f $skipbase.$_hostname ]
then
    $verbose && echo >&2 "Processing $skipbase.`hostname -s` ..."
    skip_pkg=`sed <$skipbase.$_hostname \
	    -e 's/#.*//' \
	    -e '/^[ 	]*$/d' | tr '\012' ' '`
    if [ -n "$skip_pkg" ]
    then
	$very_verbose && echo >&2 "skip on host: $skip_pkg"
	for _pkg in $skip_pkg
	do
	    _cull_from_manifest "$_pkg" "SKIP on host $_hostname"
	    mv $tmp.culled $tmp.manifest
	done
    fi
fi

# the "skip" file records packages that should NOT be installed for a
# particular distribution and version and architecture ... 
#
_parse_file $skip >$tmp.pkgs
if [ -s $tmp.pkgs ]
then
    $very_verbose && echo >&2 "skip: `cat $tmp.pkgs`"
    skip_pkg="$skip_pkg `cat $tmp.pkgs`"
    for _pkg in `cat $tmp.pkgs`
    do
	_cull_from_manifest "$_pkg" SKIP
	mv $tmp.culled $tmp.manifest
    done
fi

if $force
then
    :
else
    # If we are sure which version of Python we're using, remove lines
    # for the other versions
    #
    if [ -n "$PCP_PYTHON_PROG" ]
    then
	if which "$PCP_PYTHON_PROG" >/dev/null 2>&1
	then
	    pyver=`$PCP_PYTHON_PROG --version 2>&1 | sed -e 's/^[^0-9]*//' -e 's/\..*//'`
	    if [ -z "$pyver" ]
	    then
		echo >&2 "Warning: cannot get Python version from: `$PCP_PYTHON_PROG --version`"
	    else
		$verbose && echo >&2 "Info: guessing Python version $pyver"
		sed <$tmp.manifest >$tmp.tmp -e '/\/python[0-9]/{
/\/python'"$pyver"'[./-]/!d
}'
		if $very_verbose
		then
		    echo "packages excluded based on guessed Python version"
		    sort $tmp.manifest >$tmp.all
		    sort $tmp.tmp >$tmp.selected
		    comm -23 $tmp.all $tmp.selected
		    rm -f $tmp.all $tmp.selected
		fi
		mv $tmp.tmp $tmp.manifest
	    fi
	fi
    fi

    # If we are sure which version of Qt we're using, remove lines for
    # the other versions
    #
    # Logic from Makepkgs
    #
    cull=''
    unset QT_SELECT
    if which qtchooser >/dev/null 2>&1
    then
	if qtchooser -list-versions | grep '^5$' >/dev/null
	then
	    cull=qt4
	else
	    cull=qt5
	fi
    fi
    if [ -n "$cull" ]
    then
	#debug# echo "cull=$cull"
	grep -vi "$cull" <$tmp.manifest >$tmp.tmp
	#debug# diff $tmp.manifest $tmp.tmp
	mv $tmp.tmp $tmp.manifest
    fi
fi

# For some packaging (in particular dpkg on the Debian line), there is
# support for multiple versions of some packages in the same distro.
# This is normally designated by foo-N.M or fooNM in the package name.
# The manifest may contain the "glob" character "*" to indicate we're
# looking for the "latest" version of the package, so snoop and rewrite
# as appropriate.
#
_build_allpkgs
rm -f $tmp.sed
sed <$tmp.manifest \
    -e 's/[^[]*\[//' \
    -e 's/] *:/ /' \
    -e 's/([^)]*)//' \
| $PCP_AWK_PROG '{ for (i = 1; i < NF; i++) print $NF,$i }' \
| while read lineno pkg
do
    case "$pkg"
    in
	*\**|*\[*)
	    # $pkg includes regex me thinks
	    #
	    if [ ! -f $tmp.allpkgs ]
	    then
		echo >&2 "Warning: manifest:$lineno botch? contains pattern $pkg, but tmp.allpkgs not created from _build_allpkgs()"
	    else
		real_pkg="`grep "^$pkg\$" $tmp.allpkgs | LC_COLLATE=POSIZ sort -nr | head -1`"
		if [ -n "$real_pkg" ]
		then
		    $verbose && echo >&2 "package pattern $pkg -> $real_pkg"
		    # special note ... echo (built into bash hiding as sh)
		    # is busted on multiple platforms (like Fedora 32) ...
		    # we're using /bin/sh here to get the same results
		    # everywhere for \ interpretation (or not)
		    #
		    pkg_literal=`/bin/echo "$pkg" | sed -e 's/\[/\\\\[/g' -e 's/\*/\\\\*/g' -e 's/\./\\\\./g'`
		    /bin/echo 's/ '"$pkg_literal"' \(.*:'"$lineno"'\)$/ '"$real_pkg"' \1/' >>$tmp.sed
		fi
	    fi
	    ;;
    esac
done

if [ -s $tmp.sed ]
then
    # need [foo-*] -> [ foo-* ] -> [foo-123] and this
    # requires some epilogue and prologue sed commands
    #
    /bin/echo 's/\[/[ /' >$tmp.tmp
    /bin/echo 's/]\( :[0-9][0-9]*\)$/ ]\1/' >>$tmp.tmp
    cat $tmp.sed >>$tmp.tmp
    /bin/echo 's/ ]\( :[0-9][0-9]*\)$/]\1/' >>$tmp.tmp
    /bin/echo 's/\[ /[/' >>$tmp.tmp
    mv $tmp.tmp $tmp.sed
    if $very_verbose
    then
	echo "Pattern expansion sed script ..."
	cat $tmp.sed
    fi
    sed -f $tmp.sed <$tmp.manifest >$tmp.tmp
    if _cmp $tmp.manifest $tmp.tmp >/dev/null
    then
	: no differences
    else
	if $very_verbose
	then
	    diff $tmp.manifest $tmp.tmp
	fi
	mv $tmp.tmp $tmp.manifest
    fi
fi

if $check_manifest
then
    # save the unavailable packages list, and hand off
    #
    touch $tmp.unavail
    for _pkg in $unavail_pkg
    do
	echo "$_pkg" >>$tmp.unavail
    done
    echo "Handing off to $home/check-manifest ..."
    exec $home/check-manifest $check_manifest_args $tmp
fi

# skip "optional" and N/A packages for -b
#
if $basic
then
    $very_verbose && echo >&2 "exclude \"optional\" package lines"
    sed -e '/ optional)/d' -e '/N\/A/d' <$tmp.manifest >$tmp.tmp
    mv $tmp.tmp $tmp.manifest
fi

# main loop
#
cat $tmp.manifest \
| sed -e 's/#.*//' -e '/^[ 	]*$/d' \
| while read line
do
    apps=`echo "$line" | sed -e 's/:[0-9][0-9]*$//' -e 's/ $//'`
    lineno=`echo "$line" | sed -e 's/.* :\([0-9][0-9]*\)$/\\1/'`
    rm -f $tmp.ok
    rm -f $tmp.echo

    if $all
    then
	# don't do any of this ...
	#
	:
    else
	for app in $apps
	do
	    # leading ! negates the guard
	    case $app
	    in
		!*)
		    app=`echo "$app" | sed -e 's/^!//'`
		    negate=true
		    ;;
		*)
		    negate=false
		    ;;
	    esac
	    case $app
	    in
		\[*)
		    break
		    ;;
		*\?)
		    app=`echo $app | sed -e 's/?$//'`
		    optional=true
		    ;;
		*)
		    optional=false
		    ;;
	    esac
	    case $app
	    in
		F_pkg|S_pkg)
		    app=pkg
		    ;;
	    esac
	    case $app
	    in
		\[*)
		    break
		    ;;
		*::)
		    # special case Perl, no module name
		    echo "use `echo $app | sed -e 's/::$//'`;" | perl >/dev/null 2>&1
		    ok=$?
		    ;;

		*::*)
		    # normal case Perl, with module name
		    echo "use $app;" | perl >/dev/null 2>&1
		    ok=$?
		    ;;
		*)  # file, directory or executable tests, separated by |
		    rm -f $tmp.tmp
		    for obj in `echo "$app" | sed -e 's/|/ /g'`
		    do
			case "$obj"
			in
			    /*)
				if [ -f "$obj" -o -d "$obj" ]
				then
				    touch $tmp.tmp
				    break
				fi
				;;
			    *)
				if which $obj >/dev/null 2>&1
				then
				    touch $tmp.tmp
				    break
				fi
				;;
			esac
		    done
		    [ -f $tmp.tmp ]
		    ok=$?
		    ;;
	    esac
	    if $negate
	    then
		ok=`expr 1 - $ok`
	    fi
	    if $verbose
	    then
		$PCP_ECHO_PROG >&2 $PCP_ECHO_N "$app ... "$PCP_ECHO_C
		$optional && $PCP_ECHO_PROG >&2 $PCP_ECHO_N "[optional] "$PCP_ECHO_C
		if [ $ok = 0 ]
		then
		    $PCP_ECHO_PROG >&2 $PCP_ECHO_N "yes "$PCP_ECHO_C
		else
		    $PCP_ECHO_PROG >&2 $PCP_ECHO_N "no "$PCP_ECHO_C
		fi
		touch $tmp.echo
	    fi
	    if [ $ok = 0 ]
	    then
		if $optional
		then
		    if [ -f $tmp.echo ]
		    then
			echo >&2
			rm -f $tmp.echo
		    fi
		    continue
		fi
		touch $tmp.ok
		break
	    else
		if $optional
		then
		    # guard not true, skip checks for other apps
		    touch $tmp.ok
		    break
		fi
	    fi
	done
    fi

    if [ ! -f $tmp.ok ]
    then
	if $pkglist
	then
	    echo "$apps" \
	    | sed -n \
		-e '/\[SKIP on host/s/on host.*/]/' \
		-e 's/^[^ ][^ ]*[ 	][ 	]*\[//' \
		-e 's/][ 	]*$//' \
		-e 's/ ([^)]*)//' \
		-e 's/base [^ ]* install//' \
		-e 's/^or //' \
		-e 's/ or$//' \
		-e 's/^or$//' \
		-e 's/ or / /g' \
		-e 's/[ 	][ 	]*/ /g' \
		-e '/^ *$/d' \
		-e p \
	    | while read _missing
	    do
		# don't list the unavailable packages nor the skipped ones
		# nor (if $all is false) the ones already installed
		#
		for _need in $_missing
		do
		    if [ "$_need" = "N/A" ]
		    then
			if $verbose
			then
			    $PCP_ECHO_PROG >&2 $PCP_ECHO_N "[unavailable] "$PCP_ECHO_C
			    touch $tmp.echo
			fi
			continue
		    fi
		    if [ "$_need" = "SKIP" ]
		    then
			if $verbose
			then
			    $PCP_ECHO_PROG >&2 $PCP_ECHO_N "[skip] "$PCP_ECHO_C
			    touch $tmp.echo
			fi
			continue
		    fi
		    if ! $all && grep "^$_need\$" $tmp.installed >/dev/null
		    then
			[ -f $tmp.echo ] && echo >&2
			echo >&2 "Warning: manifest:$lineno botch? target: \"`echo "$apps" | sed -e 's/[ 	].*//'`\" not found, but package \"$_need\" already installed"
			rm -f $tmp.echo
		    else
			rm -f $tmp.match
			for _pkg in $unavail_pkg
			do
			    if [ "$_need" = "$_pkg" ]
			    then
				if $verbose
				then
				    $PCP_ECHO_PROG >&2 $PCP_ECHO_N "[unavailable] "$PCP_ECHO_C
				    touch $tmp.echo
				fi
				touch $tmp.match
				break
			    fi
			done
			if [ ! -f $tmp.match ]
			then
			    if [ -f $tmp.allpkgs ]
			    then
				_need_expand="`grep "^$_need\$" <$tmp.allpkgs`"
				if [ -n "$_need_expand" ]
				then
				    echo "$_need_expand"
				else
				    echo "$_need"
				fi
			    else
				echo "$_need"
			    fi
			fi
		    fi
		done
	    done
	elif echo "$apps" | grep '\[SKIP' >/dev/null
	then
	    # nothing to report
	    :
	else
	    if $all
	    then
		tag=''
	    else
		tag='Missing: '
	    fi
	    echo "$tag`echo "$apps" \
		| sed \
		    -e 's/[ 	][ 	]*/ /g' \
		    -e '/ /{
s/? /?@/
:loop1
s/\(\[.*\) /\1@/
t loop1
:loop2
s/ \([^[]\)/@|@\1/
t loop2
s/@/ /g
}'`"
	fi
    else
	if echo "$apps" | grep 'N/A' >/dev/null
	then
	    [ -f $tmp.echo ] && echo >&2
	    echo >&2 "Warning: manifest:$lineno botch? target: \"`echo "$apps" | sed -e 's/[ 	].*//'`\" found, but marked N/A"
	    rm -f $tmp.echo
	fi
    fi
    [ -f $tmp.echo ] && echo >&2

done >$tmp.out


if $pkglist
then
    # append required packages ... uniq below culls any duplicates
    for _pkg in $require_pkg
    do
	echo "$_pkg" >>$tmp.out
    done
fi

# for -M, remove all the meta-packages, like cpan(...) and pip3(...)
#
if $include_meta
then
    :
else
    sed <$tmp.out >$tmp.tmp \
	-e '/^cpan(/d' \
	-e '/^pip3(/d' \
    # end
    mv $tmp.tmp $tmp.out
fi

if [ -s $tmp.out ]
then
    if $pkglist
    then
	sort <$tmp.out \
	| uniq >$tmp.tmp
	if $pkgcmd
	then
	    grep -v 'cpan(' <$tmp.tmp | grep -v 'pip3(' >$tmp.pkg
	    if [ -s $tmp.pkg ]
	    then
		echo "sudo $installcmd `tr '\012' ' ' <$tmp.pkg`"
	    fi
	    grep 'cpan(' <$tmp.tmp >$tmp.pkg
	    if [ -s $tmp.pkg ]
	    then
		sed <$tmp.pkg \
		    -e 's/^/sudo cpan /' \
		    -e 's/cpan(/"/' \
		    -e 's/)/"/'
		# end
	    fi
	    grep 'pip3(' <$tmp.tmp >$tmp.pkg
	    if [ -s $tmp.pkg ]
	    then
		echo "sudo pip3 install `tr '\012' ' ' <$tmp.pkg`"
	    fi
	else
	    tr '\012' ' ' <$tmp.tmp \
	    | sed -e 's/  */ /g' -e 's/^ //' -e 's/ $//'
	    # BSD* and Darwin heuristic hack ... may not need extra echo
	    # for these guys, but ... needed on
	    # FreeBSD 12.0
	    #
	    echo
	fi
    else
	cat $tmp.out
    fi
fi

$pkglist && exit

# Any required packages not installed?
#
for _pkg in $require_pkg
do
    echo "Missing: $_pkg [required package not installed]"
done

if which slackpkg >/dev/null 2>&1
then
    # Slackware ...
    :
elif which rpm >/dev/null 2>&1
then
    # RPM based, there are some version dependencies in the spec
    # file (see BuildRequires: lines build/rpm/pcp.spec.in) and some
    # are found in configure.ac ... both need to be mirrored here
    #
    cat <<End-of-File >$tmp.rpm
# one line per rpm
# rpm-name	relop	version	pcp-pkg
# text after # is treated as a comment
# pcp.spec.in
qt-devel|qt4-devel|libqt4-devel|lib64qt4-devel	>=	4.4
libpfm-devel				>=	4.4	pcp-pmda-perfevent
libpfm|libpfm4				>=	4.4	pcp-pmda-perfevent
libibmad-devel|infiniband-diags-devel	>=	1.1.7	pcp-pmda-infiniband
libibmad|libibmad5|infiniband-diags	>=	1.1.7	pcp-pmda-infiniband
libibumad-devel|rdma-core-devel		>=	1.1.7	pcp-pmda-infiniband
libibumad|libibumad3			>=	1.1.7	pcp-pmda-infiniband
End-of-File
    cat $tmp.rpm \
    | sed -e 's/#.*//' -e '/^[ 	]*$/d' \
    | while read rpmlist relop specversion pcp_pkg
    do
	[ -n "$pcp_pkg" ] && pcp_pkg=" for $pcp_pkg"
	rm -f $tmp.found $tmp.notfound
	for rpm in `echo "$rpmlist" | sed -e 's/|/ /g'`
	do
	    rpm -q $rpm >$tmp.tmp 2>/dev/null
	    if grep 'is not installed' $tmp.tmp >/dev/null 2>&1
	    then
		$verbose && echo >&2 "$rpm: not installed, need $relop $specversion$pcp_pkg, OK"
		echo >&2 "Warning: $rpm not installed, need $relop $specversion$pcp_pkg" >>$tmp.notfound
	    else
		touch $tmp.found
		version=`sed <$tmp.tmp -e "s/^$rpm-//" -e 's/-.*//'`
		( echo $version; echo $specversion ) | _compare $relop
		if [ $? = 0 ]
		then
		    $verbose && echo >&2 "$rpm: version installed $version, need $relop $specversion$pcp_pkg, OK"
		else
		    echo >&2 "Warning: $rpm version installed $version, need $relop $specversion$pcp_pkg"
		fi
	    fi
	done
	if [ -f $tmp.found ]
	then
	    :
	else
	    $verbose || cat >&2 $tmp.notfound
	fi
    done
fi

if which pkg-config >/dev/null 2>&1
then
    # PKG_CHECK_MODULES() in configure.ac
    #
    cat <<End-of-File >$tmp.pkg-config
# one line per rpm
# lib-name	relop	version	pcp-pkg
# text after # is treated as a comment
libuv           >=	1.0
End-of-File
    cat $tmp.pkg-config \
    | sed -e 's/#.*//' -e '/^[ 	]*$/d' \
    | while read lib relop version pcp_pkg
    do
	[ -n "$pcp_pkg" ] && pcp_pkg=" for $pcp_pkg"
	libversion=`pkg-config --modversion "$lib" 2>/dev/null`
	if [ -z "$libversion" ]
	then
	    echo >&2 "Warning: Package $lib not known to pkg-config , need $relop $version$pcp_pkg"
	else
	    ( echo $libversion; echo $version ) | _compare $relop
	    if [ $? = 0 ]
	    then
		$verbose && echo >&2 "$lib: version installed $libversion, need $relop $version$pcp_pkg, OK"
	    else
		echo >&2 "Warning: $lib version installed $libversion, need $relop $version$pcp_pkg"
	    fi
	fi
    done
fi

host=`hostname`
_check_host $host
if which pmhostname >/dev/null 2>&1
then
    pmhost=`pmhostname`
    if [ -z "$pmhost" ]
    then
	echo >&2 "Warning: pmhostname returns nothing!"
    else
	case $pmhost
	in
	    $host|$host.*)
		    ;;
	    *)
		    echo >&2 "Warning: hostname ($host) is not a prefix of pmhostname ($pmhost)"
		    ;;
	esac
	_check_host $pmhost
    fi
fi

if [ -n "$PCP_VAR_DIR" ]
then
    # need QA access to pmlogger via pmlc from local subnet
    #
    network=`_getnetworkaddr`
    if [ -n "$network" ]
    then
	if [ -f $PCP_VAR_DIR/config/pmlogger/config.default ]
	then
	    if grep -q "allow $network" $PCP_VAR_DIR/config/pmlogger/config.default
	    then
		:
	    else
		echo "Missing: \"allow $network : all;\" [access] in $PCP_VAR_DIR/config/pmlogger/config.default"
		echo "Use \"$ sudo -E .../qa/admin/allow-pmlc-access\" to fix this."
	    fi
	else
	    echo >&2 "Warning: \"$PCP_VAR_DIR/config/pcp/pmlogger/config.default\" is missing"
	fi
    else
	echo >&2 "Please ignore Warnings from _getnetworkaddr unless you wish to run the"
	echo >&2 "full PCP QA suite."
    fi
else
    echo >&2 "Warning: \"/etc/pcp.conf\" is missing"
fi

if sudo -u pcp id >/dev/null
then
    # pcp user appears to exist ...
    #
    sudo -u pcp [ -x $HOME ] || echo "Error: $HOME is not searchable by user \"pcp\""
fi

# Now some platform-specific tests
#
case "$distro"
in
    OpenBSD)
	if false
	then
	    # redundant now the openbsd PMDA no longer reads /dev/mem
	    # directly
	    #
	    allowkmem=`sysctl kern.allowkmem | sed -e 's/.*=//'`
	    if [ "$allowkmem" != 1 ]
	    then
		echo >&2 "Warning: kern.allowkmem is \"$allowkmem\" not 1 and so openbsd PMDA will not be able"
		echo "         to access /dev/kmem"
		echo "         Suggest adding kern.allowkmem=1 to etc/sysctl.conf and rebooting."
	    fi
	fi
	;;

esac

$very_verbose && echo >&2 "temp files:" $tmp.*
